#include "ClientManager.h"
#include <ctime>


ClientManager::ClientManager(const QString& ip, int port, QGraphicsScene * scene): IManager(scene)
{
    connected = false;
    m_ptr_gameData = new GameData();
    InitializeCriticalSectionAndSpinCount(&m_cs_updating, 0x400);
    InitializeCriticalSectionAndSpinCount(&m_cs_command, 0x400);
    m_ptr_messageManager = new ClientMessageManager();
    m_ptr_messageManager->connectToSocket(ip, port);


    //connecting signals from ClientManager to ClientMessageManager
    connect(this, SIGNAL(signalFire()),
            m_ptr_messageManager, SLOT(slotFIRE()));

    connect(this, SIGNAL(signalMove()),
            m_ptr_messageManager, SLOT(slotMOVE()));

    connect(this, SIGNAL(signalStopFire()),
            m_ptr_messageManager, SLOT(slotSTOP_FIRE()));

    connect(this, SIGNAL(signalStopMove()),
            m_ptr_messageManager, SLOT(slotSTOP_MOVE()));

    connect(this, SIGNAL(signalTurn(char)),
            m_ptr_messageManager, SLOT(slotTURN(char)));

    connect(this, SIGNAL(signalDisconnect()),
            m_ptr_messageManager, SLOT(slotDISCONNECT()));

    connect(this, SIGNAL(signalSendNickname(QString)),
            m_ptr_messageManager, SLOT(slotSEND_NICK(QString)));

    //connecting signals from ClientMessageManager to ClientManager
    connect(m_ptr_messageManager, SIGNAL(signalConnectionSuccessfull()),
            this, SLOT(slotConnectionSuccessfull()));
    connect(m_ptr_messageManager, SIGNAL(signalSC_CONN_ACCEPTED(int)),
            this, SLOT(slotNicknameAccepted(int)));

    connect(m_ptr_messageManager, SIGNAL(signalSC_CONN_REJECTED(QString)),
            this, SLOT(slotNicknameRejected(QString)));

    connect(m_ptr_messageManager, SIGNAL(signalSC_PLAYERLIST(QStringList)),
            this, SLOT(slotRecievePlaylist(QStringList)));

    connect(m_ptr_messageManager, SIGNAL(signalSC_GAME_START()),
            this, SLOT(slotStart()));

    connect(m_ptr_messageManager, SIGNAL(signalSC_GAME_END(int)),
            this, SLOT(slotGameOver(int)));





    connect(m_ptr_messageManager, SIGNAL(signalSC_FIRE(int)),
            this, SLOT(slotFire(int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_MOVE(int)),
            this, SLOT(slotMove(int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_STOP_FIRE(int)),
            this, SLOT(slotStopFire(int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_STOP_MOVE(int)),
            this, SLOT(slotStopMove(int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_TURN(int,char)),
            this, SLOT(slotTurn(int,char)));

    connect(m_ptr_messageManager, SIGNAL(signalSC_SEND_MAP(int,int,QString,int,int,int,int)),
            this, SLOT(slotCreateMap(int,int,QString,int,int,int,int)));

    connect(m_ptr_messageManager, SIGNAL(signalSC_START_SNAPSHOT()),
            this, SLOT(slotStartSnapshot()));
    connect(m_ptr_messageManager, SIGNAL(signalSC_SET_TANK(int,int,int,int,char,char,int)),
            this, SLOT(slotSetTank(int,int,int,int,char,char,int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_SET_BULLET(int,int,int,char,int)),
            this, SLOT(slotSetBullet(int,int,int,char,int)));
    connect(m_ptr_messageManager, SIGNAL(signalSC_END_SNAPSHOT()),
            this, SLOT(slotFinishSnapshot()));
    //connect(m_ptr_messageManager,SIGNAL(signalSC))
}

ClientManager::~ClientManager()
{
    emit signalDisconnect();

    delete m_ptr_messageManager;
}


void ClientManager::slotHostConnected()
{
    connected = true;
    emit signalHostConnected();
}


void ClientManager::slotHostFailedConnection()
{
    emit signalHostFailedConnection();
}


void ClientManager::slotStartSnapshot()
{
    EnterCriticalSection(&m_cs_updating);
    qDebug() << "ClientManager::slotStartSnapshot()";

    m_timestamp = clock();
    max_bullet = 0;
    m_ptr_gameData->bullets.clear();
}


void ClientManager::slotFinishSnapshot()
{
    qDebug() << "ClientManager::slotFinishSnapshot()";
    if (game_status < GAME_IN_PROC)
        game_status = GAME_IN_PROC;
    LeaveCriticalSection(&m_cs_updating);
}


void ClientManager::slotSetBullet(int bullet_id, int pos_x, int pos_y, char direction, int progress)
{
    EnterCriticalSection(&m_cs_command);

    Bullet bullet(bullet_id, m_bullet_speed, pos_x, pos_y, direction, progress);
    bullet.addToScene(m_ptr_scene);
    m_ptr_gameData->bullets.push_back(bullet);

    if (bullet_id >= max_bullet)
        max_bullet = bullet_id + 1;

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotSetTank(int tank_id, int health, int pos_x, int pos_y, char action, char direction, int progress)
{
    EnterCriticalSection(&m_cs_command);
    qDebug() << "ClientManager::slotSetTank(int tank_id, int health, int pos_x, int pos_y, char action, char direction, int progress)";
    qDebug() << "tank_id ="<<tank_id;
    qDebug() << "health ="<<health;
    qDebug() << "pos_x ="<<pos_x;
    qDebug() << "pos_y ="<<pos_y;
    qDebug() << "action ="<<action;
    qDebug() << "direction ="<<direction;
    qDebug() << "progress ="<<progress;

    Tank tank(pos_x,pos_y,direction,2,0,tank_id,3);
//    for (iter = m_ptr_gameData->tanks.begin(); iter != m_ptr_gameData->tanks.end(); ++iter)
//    {
//        if (iter->GetID() == tank_id)
//            break;
//    }
//    if (iter == m_ptr_gameData->tanks.end())
//    {
//        LeaveCriticalSection(&m_cs_command);
//        return; // Tank not found
//    }

    tank.SetHealth(health);

    tank.SetCurrentAction(action);


    m_ptr_gameData->tanks.push_back(tank);
    // !!! Next lines are not clear
    //std::map<int, Player>::iterator pl = m_players.find(tank_id);
    //pl->second.action = action;
    //pl->second.action_queue.clear();

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotSetNickname(QString nick)
{
    EnterCriticalSection(&m_cs_updating);

    qDebug()<< "slotSetNickname "<< game_status <<" "<<GAME_SETTING ;
    if (m_ptr_gameData == NULL || game_status > GAME_SETTING)
    {
        LeaveCriticalSection(&m_cs_updating);
        return;
    }

    if (!testNickname(nick))
    {
        qDebug()<<nick <<"bad";
        LeaveCriticalSection(&m_cs_updating);
        emit signalNicknameRejected("Bad_nickname");
        return;
    }

    m_ptr_gameData->my_nick = nick;
    m_ptr_gameData->my_id = -1;
    qDebug()<<nick <<"good";
    emit signalSendNickname(nick);

    LeaveCriticalSection(&m_cs_updating);
}


void ClientManager::slotNicknameAccepted(int player_id)
{
    m_ptr_gameData->my_id = player_id;
    emit signalNicknameAccepted(player_id);
}


void ClientManager::slotNicknameRejected(QString message)
{
    emit signalNicknameRejected(message);
}


void ClientManager::slotGameOver(int player_id)
{
    game_status = GAME_OVER;
    emit signalGameOver(player_id);
}

void ClientManager::slotConnectionSuccessfull()
{
    connected = true;
}


void ClientManager::slotDisconnect()
{
    emit signalDisconnect();
}


void ClientManager::slotRecievePlaylist(QStringList list)
{
    emit signalPlaylist(list);
}


void ClientManager::slotUpdate()
{
    EnterCriticalSection(&m_cs_updating);
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        LeaveCriticalSection(&m_cs_updating);
        return;
    }

    bool need_snapshot;
    long timestamp = clock();
    while (timestamp > m_timestamp)
        advance(timestamp, need_snapshot);

    m_ptr_gameData->DrawAll();

    LeaveCriticalSection(&m_cs_command);
    LeaveCriticalSection(&m_cs_updating);
}


void ClientManager::slotStart()
{
    EnterCriticalSection(&m_cs_command);

    if (m_ptr_gameData == NULL)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    if (m_timestamp < 0)
    {
        game_status = GAME_STARTED;
        m_timestamp = clock();
        max_bullet = 0;
    }

    LeaveCriticalSection(&m_cs_command);
    emit signalStartClientGame();
}


void ClientManager::slotFire(int tank_id)
{
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    std::map<int, Player>::iterator pl = m_players.find(tank_id);

    if (pl->second.firing)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    pl->second.firing = true;

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotStopFire(int tank_id)
{
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    std::map<int, Player>::iterator pl = m_players.find(tank_id);

    if (! pl->second.firing)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    pl->second.firing = false;

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotMove(int tank_id)
{
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    emit signalMove();

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotStopMove(int tank_id)
{
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    emit signalStopMove();

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotTurn(int tank_id, char direction)
{
    EnterCriticalSection(&m_cs_command);

    if (game_status != GAME_IN_PROC)
    {
        LeaveCriticalSection(&m_cs_command);
        return;
    }

    emit signalTurn(direction);

    LeaveCriticalSection(&m_cs_command);
}


void ClientManager::slotKeyPresEvent(QKeyEvent *event)
{
    EnterCriticalSection(&m_cs_updating);

    if (game_status == GAME_IN_PROC)
    {
        int iDir;
        switch (event->key())
        {
        case Qt::Key_Control:
            emit signalFire();
            LeaveCriticalSection(&m_cs_updating);
            return;
        case Qt::Key_Left:
            iDir = 1;
            break;
        case Qt::Key_Right:
            iDir = 3;
            break;
        case Qt::Key_Up:
            iDir = 0;
            break;
        case Qt::Key_Down:
            iDir = 2;
            break;
        default:
            LeaveCriticalSection(&m_cs_updating);
            return;
        }
        std::map<int, Player>::iterator pl = m_players.find(m_ptr_gameData->my_id);
        std::list<Tank>::iterator tank = m_ptr_gameData->tanks.begin();
        while (tank->GetID() != m_ptr_gameData->my_id)
            ++tank;

        int change;
        switch (tank->GetDirection())
        {
        case 'U':
            change = 0;
            break;
        case 'R':
            change = 1;
            break;
        case 'D':
            change = 2;
            break;
        default:
            change = 3;
        }
        if (pl->second.action != 'S')
        {
            if (pl->second.action == 'R')
                change++;
            else if (pl->second.action == 'L')
                change--;
            for (int i = 0; i < pl->second.action_queue.length(); ++i)
            {
                if (pl->second.action_queue.at(i).toAscii() == 'R')
                    change++;
                else if (pl->second.action_queue.at(i).toAscii() == 'L')
                    change--;
            }
        }
        while (change < 0)
            change += 4;
        change %= 4;

        change = iDir - change;
        if (change < -2)
            change += 4;
        if (change > 2)
            change -= 4;
        while (change > 0)
        {
            change--;
            emit signalTurn('R');
        }
        while (change < 0)
        {
            change++;
            emit signalTurn('L');
        }

        emit signalMove();

        if (event->modifiers() & Qt::ControlModifier)
        {
            emit signalFire();
        }
        else
        {
            emit signalStopFire();
        }
    }

    LeaveCriticalSection(&m_cs_updating);
}


void ClientManager::slotKeyReleaseEvent(QKeyEvent *event)
{
    EnterCriticalSection(&m_cs_updating);

    if (game_status == GAME_IN_PROC)
    {
        switch (event->key())
        {
        case Qt::Key_Control:
            emit signalStopFire();
            break;
        case Qt::Key_Left:
        case Qt::Key_Right:
        case Qt::Key_Up:
        case Qt::Key_Down:
            emit signalStopMove();
            if (!(event->modifiers() & Qt::ControlModifier))
                emit signalStopFire();
        }
    }

    LeaveCriticalSection(&m_cs_updating);
}

bool ClientManager::Connected()
{
    return connected;
}
